package com.mybatisflex.example.service.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.example.controller.response.DistrictCityRsp;
import com.mybatisflex.example.controller.response.DistrictRsp;
import com.mybatisflex.example.mapper.mysql.DistrictMapper;
import com.mybatisflex.example.pojo.domain.mysql.District;
import com.mybatisflex.example.pojo.domain.table.DistrictTableDef;
import com.mybatisflex.example.pojo.enums.DistrictLevelEnum;
import com.mybatisflex.example.pojo.enums.DistrictSubdistrictEnum;
import com.mybatisflex.example.service.DistrictService;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 行政区信息 服务层实现。
 *
 * @author tanyunpeng
 * @since 2024-02-28
 */
@Service
public class DistrictServiceImpl extends ServiceImpl<DistrictMapper, District> implements DistrictService {

    @Override
    public District findByCode(Integer code) {
        if (Objects.isNull(code)) {
            return null;
        }
        return queryChain().select().from(DistrictTableDef.DISTRICT).where(DistrictTableDef.DISTRICT.CODE.eq(code)).limit(1).one();
    }

    @Override
    public List<District> findByNameLike(String name) {
        if (StringUtils.isBlank(name)) {
            return Lists.newArrayList();
        }
        return queryChain().select().from(DistrictTableDef.DISTRICT).where(DistrictTableDef.DISTRICT.NAME.likeLeft(name)).list();
    }

    @Override
    public List<District> findByParentCodeIn(Collection<Integer> parentCodes) {
        if (CollectionUtils.isEmpty(parentCodes)) {
            return Lists.newArrayList();
        }
        return queryChain().select().from(DistrictTableDef.DISTRICT).where(DistrictTableDef.DISTRICT.PARENT_CODE.in(parentCodes)).list();
    }

    @Override
    public List<DistrictRsp> district(String keywords, Integer subDistrict) {
        List<District> list = Lists.newArrayList();
        if (StringUtils.isBlank(keywords)) {
            District districtDO = findByCode(100000);
            list.add(districtDO);
        } else if (StringUtils.isNumeric(keywords)) {
            District districtDO = findByCode(Integer.valueOf(keywords));
            list.add(districtDO);
        } else {
            List<District> districtDOList = findByNameLike(keywords);
            if (CollectionUtil.isNotEmpty(districtDOList)) {
                list.addAll(districtDOList);
            }
        }
        List<DistrictRsp> reList = toRspList(list);
        buildChildren(reList, subDistrict, DistrictSubdistrictEnum.NONE.getCode());
        return reList;
    }

    private void buildChildren(List<DistrictRsp> list, Integer subdistrict, Integer level) {
        if (CollectionUtil.isEmpty(list)) {
            return;
        }
        if (Objects.isNull(subdistrict) || subdistrict <= level) {
            return;
        }
        Set<Integer> adCodeSet = list.stream().map(DistrictRsp::getCode).collect(Collectors.toSet());
        List<District> districtDOList = findByParentCodeIn(adCodeSet);
        Map<Integer, List<District>> districtDOMap =
                districtDOList.stream().collect(Collectors.groupingBy(District::getParentCode));
        for (DistrictRsp districtRsp : list) {
            List<District> subList = districtDOMap.get(districtRsp.getCode());
            List<DistrictRsp> subRspList = toRspList(subList);
            districtRsp.getDistricts().addAll(subRspList);
            buildChildren(subRspList, subdistrict, level + 1);
        }
    }

    @Override
    public List<DistrictCityRsp> initial(Integer subDistrict) {
        Set<Integer> levels = Sets.newHashSet(DistrictLevelEnum.CITY.getCode());
        if (Objects.equals(subDistrict, DistrictLevelEnum.DISTRICT.getCode())) {
            levels.add(DistrictLevelEnum.DISTRICT.getCode());
        }
        List<District> list = queryChain().select().from(DistrictTableDef.DISTRICT).where(DistrictTableDef.DISTRICT.LEVEL.in(levels)).list();
        Map<String, List<DistrictRsp>> map = Maps.newHashMap();
        for (District district : list) {
            DistrictRsp districtRsp = toRsp(district);
            String initial = StringUtils.substringBefore(district.getPinyin(), 1).toUpperCase(Locale.ROOT);
            List<DistrictRsp> subList = map.getOrDefault(initial, Lists.newArrayList());
            subList.add(districtRsp);
            map.put(initial, subList);
        }
        List<DistrictCityRsp> reList = Lists.newArrayList();
        map.forEach((k, v) -> {
            DistrictCityRsp districtCityRsp = new DistrictCityRsp();
            districtCityRsp.setInitial(k);
            districtCityRsp.setDistricts(v);
            reList.add(districtCityRsp);
        });

        return reList.stream().sorted(Comparator.comparing(DistrictCityRsp::getInitial)).collect(Collectors.toList());
    }

    @Override
    public List<DistrictRsp> getDistrictByLevel(Integer level) {
        List<District> list = queryChain().select().from(DistrictTableDef.DISTRICT).where(DistrictTableDef.DISTRICT.LEVEL.eq(level)).list();
        return toRspList(list);
    }

    private DistrictRsp toRsp(District districtDO) {
        if (Objects.isNull(districtDO)) {
            return null;
        }
        DistrictRsp districtRsp = new DistrictRsp();
        districtRsp.setCode(districtDO.getCode());
        districtRsp.setName(districtDO.getName());
        districtRsp.setCenter(districtDO.getCenter());
        districtRsp.setPinyin(districtDO.getPinyin());
        districtRsp.setLevel(districtDO.getLevel());
        districtRsp.setDistricts(Lists.newArrayList());
        return districtRsp;
    }

    public List<DistrictRsp> toRspList(List<District> list) {
        if (CollectionUtils.isEmpty(list)) {
            return Lists.newArrayList();
        }
        return list.stream().map(this::toRsp).collect(Collectors.toList());
    }
}
